use darling::{FromDeriveInput, FromMeta};
use proc_macro::TokenStream;
use proc_macro2::TokenStream as T2;
use quote::{format_ident, quote};
use syn::{parse_macro_input, DeriveInput};

use crate::{periph2::rcc::RccAxB, periph2::rcc};

#[derive(Debug, FromDeriveInput)]
#[darling(attributes(spi))]
pub struct SpiConfig {
    init: String,
    spi: u8,
    size: u8,
    baud_div: u16,
    cpol: CPOL,
    cpha: CPHA,
}

#[derive(Debug, FromMeta)]
enum CPOL {
    #[darling(rename = "low")]
    LOW,
    #[darling(rename = "high")]
    HIGH,
}

#[derive(Debug, FromMeta)]
enum CPHA {
    #[darling(rename = "one")]
    ONE,
    #[darling(rename = "two")]
    TWO,
}

impl SpiConfig {

    pub fn build(&self) -> T2 {
        let rcc_axb = match self.spi {
            1 => RccAxB::APB2,
            2 | 3 => RccAxB::APB1,
            _ => panic!("invalid spi")
        };
        let rcc_field = format_ident!("spi{}en", self.spi);
        let rcc_src = rcc::rcc_enable(rcc_axb, &rcc_field, false);
        let init_fn = format_ident!("{}", self.init);
        let spi = format_ident!("SPI{}", self.spi);
        let div = format_ident!("div{}", self.baud_div);
        let cpol = match self.cpol {
            CPOL::LOW => format_ident!("idle_low"),
            CPOL::HIGH => format_ident!("idle_high")
        };
        let cpha = match self.cpha {
            CPHA::ONE => format_ident!("first_edge"),
            CPHA::TWO => format_ident!("second_edge")
        };
        let dff = match self.size {
            8 => format_ident!("eight_bit"),
            16 => format_ident!("sixteen_bit"),
            _ => panic!("invalid size"),
        };
        let rw_fn = format_ident!("spi{}_transfer", self.spi);
        let rw_type = match self.size {
            8 => format_ident!("u8"),
            16 => format_ident!("u16"),
            _ => panic!("invalid size"),
        };
        quote! {
            pub fn #init_fn() {
                #rcc_src
                let spi: stm32f1::stm32f103::#spi = unsafe { ::core::mem::transmute(()) };
                spi.cr1.reset();
                spi.cr2.reset();
                spi.i2scfgr.reset();
                spi.cr1.write(|w| {
                    w.ssm()
                        .enabled()
                        .ssi()
                        .slave_not_selected()
                        .spe()
                        .enabled()
                        .br()
                        .#div()
                        .mstr()
                        .master()
                        .cpol().#cpol()
                        .cpha().#cpha()
                        .dff().#dff()
                });
            }

            pub fn #rw_fn(value: #rw_type) -> #rw_type {
                let spi: stm32f1::stm32f103::#spi = unsafe { core::mem::transmute(()) };
                while spi.sr.read().txe().is_not_empty() {}
                spi.dr.write(|w| w.dr().bits(value as u16));
                while spi.sr.read().rxne().is_empty() {}
                spi.dr.read().dr().bits() as #rw_type
            }
        }
    }
}

pub fn build(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let config = SpiConfig::from_derive_input(&input).unwrap();
    let name = &input.ident;
    let r = config.build();

    let expand = quote! {

        impl #name {    
            #r
        }
    };
    expand.into()
}






